package org.csu.softwaremetrics_demo.metric.ck;

/**
 * LCOM (Lack of Cohesion of Methods) 类
 * 用于计算类内部方法之间的内聚性
 * <p>
 * LCOM度量方法间共享属性的程度，计算方法：
 * LCOM = |P| - |Q| if |P| - |Q| > 0, 否则为0
 * 其中:
 * - P = 不共享任何属性的方法对的数量
 * - Q = 共享至少一个属性的方法对的数量
 * <p>
 * 高LCOM值表示类的内聚性低，可能需要被拆分成多个类
 * 低LCOM值表示类的内聚性高，设计良好
 */

import org.csu.softwaremetrics_demo.entity.common.CKMetricSingleClass;
import org.csu.softwaremetrics_demo.entity.result.Result;
import org.csu.softwaremetrics_demo.metric.Metric;
import org.eclipse.jdt.core.dom.*;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class LCOM extends ASTVisitor implements Metric<CKMetricSingleClass> {

    /**
     * 存储每个方法访问的字段集合
     * 每个TreeSet代表一个方法访问的所有字段
     */
    ArrayList<TreeSet<String>> methods = new ArrayList<TreeSet<String>>();

    /**
     * 存储类中声明的所有字段名称
     */
    Set<String> declaredFields;

    /**
     * 构造函数，初始化declaredFields
     */
    public LCOM() {
        this.declaredFields = new HashSet<String>();
    }

    /**
     * 访问FieldDeclaration节点，记录类中声明的字段
     *
     * @param node FieldDeclaration节点
     * @return 是否继续访问子节点
     */
    public boolean visit(FieldDeclaration node) {
        for (Object o : node.fragments()) {
            VariableDeclarationFragment vdf = (VariableDeclarationFragment) o;
            declaredFields.add(vdf.getName().toString());
        }
        return super.visit(node);
    }

    /**
     * 访问SimpleName节点，记录方法访问的字段
     *
     * @param node SimpleName节点
     * @return 是否继续访问子节点
     */
    public boolean visit(SimpleName node) {
        String name = node.getFullyQualifiedName();
        if (declaredFields.contains(name)) {
            acessed(name);
        }
        return super.visit(node);
    }

    /**
     * 记录当前正在分析的方法访问了指定的字段
     *
     * @param name 被访问的字段名称
     */
    private void acessed(String name) {
        if (!methods.isEmpty()) {
            methods.get(methods.size() - 1).add(name);
        }
    }

    /**
     * 访问MethodDeclaration节点，初始化方法访问的字段集合
     *
     * @param node MethodDeclaration节点
     * @return 是否继续访问子节点
     */
    public boolean visit(MethodDeclaration node) {
        methods.add(new TreeSet<String>());
        return super.visit(node);
    }

    /**
     * 执行LCOM指标计算
     *
     * @param cu           CompilationUnit对象
     * @param singleMetric CKMetricSingleClass对象
     * @param result       结果对象
     */
    @Override
    public void execute(CompilationUnit cu, CKMetricSingleClass singleMetric, Result result) {
        cu.accept(this);
    }

    /**
     * 设置单一指标
     *
     * @param singleMetric CKMetricSingleClass对象
     */
    @Override
    public void setSingleMetric(CKMetricSingleClass singleMetric) {

        /*
         * LCOM = |P| - |Q| if |P| - |Q| > 0
         * where
         * P = set of all empty set intersections
         * Q = set of all nonempty set intersections
         */

        // extracted from https://github.com/dspinellis/ckjm
        int lcom = 0;
        for (int i = 0; i < methods.size(); i++)
            for (int j = i + 1; j < methods.size(); j++) {

                TreeSet<?> intersection = (TreeSet<?>) methods.get(i).clone();
                intersection.retainAll(methods.get(j));
                if (intersection.size() == 0) lcom++;
                else lcom--;
            }
        singleMetric.setLcom(lcom > 0 ? lcom : 0);
    }

}
